package com.lkd.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.lkd.common.VMSystem;
import com.lkd.config.TopicConfig;
import com.lkd.contract.SupplyCfg;
import com.lkd.contract.SupplyChannel;
import com.lkd.contract.TaskCompleteContract;
import com.lkd.dao.TaskDao;
import com.lkd.emq.MqttProducer;
import com.lkd.entity.TaskDetailsEntity;
import com.lkd.entity.TaskEntity;
import com.lkd.entity.TaskStatusTypeEntity;
import com.lkd.exception.LogicException;
import com.lkd.feignService.UserService;
import com.lkd.feignService.VMService;
import com.lkd.http.viewModel.CancelTaskViewModel;
import com.lkd.http.viewModel.RankViewModel;
import com.lkd.http.viewModel.TaskViewModel;
import com.lkd.service.TaskDetailsService;
import com.lkd.service.TaskService;
import com.lkd.service.TaskStatusTypeService;
import com.lkd.utils.RedisUtils;
import com.lkd.utils.UserRoleUtils;
import com.lkd.viewmodel.Pager;
import com.lkd.viewmodel.UserViewModel;
import com.lkd.viewmodel.UserWork;
import com.lkd.viewmodel.VendingMachineViewModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Service
@Slf4j
public class TaskServiceImpl extends ServiceImpl<TaskDao,TaskEntity> implements TaskService{


    @Autowired
    private TaskDetailsService taskDetailsService;

    @Autowired
    private VMService vmService;

    @Autowired
    private TaskStatusTypeService statusTypeService;

    @Autowired
    private UserService userService;


    @Autowired
    private MqttProducer mqttProducer;

    /**
     * todo : 做压力测试,幂等性,会出现相同工单编码的问题嘛?
     * 检查是否可以创建工单
     * @param taskViewModel : 工单视图模型
     * @return :
     * @throws LogicException :
     */
    @Override
    @Transactional(rollbackFor = {Exception.class},noRollbackFor = {LogicException.class})
    public boolean createTask(TaskViewModel taskViewModel) throws LogicException {
        checkCreateTask(taskViewModel.getInnerCode(),taskViewModel.getProductType());
        if(hasTask(taskViewModel.getInnerCode(),taskViewModel.getProductType())) {
            throw new LogicException("该机器有未完成的同类型工单");
        }
        //新增工单表记录
        TaskEntity taskEntity = new TaskEntity();
        BeanUtils.copyProperties(taskViewModel,taskEntity);
        taskEntity.setTaskCode(this.generateTaskCode());
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_CREATE);
        taskEntity.setProductTypeId(taskViewModel.getProductType());
        String userName = userService.getUser(taskViewModel.getUserId()).getUserName();
        taskEntity.setUserName(userName);
        taskEntity.setAddr(vmService.getVMInfo(taskViewModel.getInnerCode()).getNodeAddr());
        this.save(taskEntity);

        //如果是补货工单，向 工单明细表插入记录
        if(Objects.equals(taskEntity.getProductTypeId(), VMSystem.TASK_TYPE_SUPPLY)){
            taskViewModel.getDetails().forEach(d->{
                TaskDetailsEntity detailsEntity = new TaskDetailsEntity();
                BeanUtils.copyProperties( d,detailsEntity );
                detailsEntity.setTaskId(taskEntity.getTaskId());
                taskDetailsService.save(detailsEntity);
            });
        }

        return true;
    }

    @Override
    public Boolean accept(Long id) {
        //查询工单
        TaskEntity task = this.getById(id);
        if(!Objects.equals(task.getTaskStatus(), VMSystem.TASK_STATUS_CREATE)){
            throw new LogicException("工单状态不是待处理");
        }
        //修改工单状态为进行
        task.setTaskStatus( VMSystem.TASK_STATUS_PROGRESS );
        return this.updateById(task);
    }

    @Override
    public boolean completeTask(long id) {
        TaskEntity taskEntity = this.getById(id);
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_FINISH);
        this.updateById(taskEntity);
      //向消息队列发送消息，通知售货机更改状态
       //封装协议
        TaskCompleteContract taskCompleteContract=new TaskCompleteContract();
        taskCompleteContract.setInnerCode(taskEntity.getInnerCode());//售货机编号
        taskCompleteContract.setTaskType( taskEntity.getProductTypeId() );//工单类型
        //发送到emq
        try {
            mqttProducer.send(TopicConfig.COMPLETED_TASK_TOPIC,2, taskCompleteContract );
        } catch (Exception e) {
            log.error("发送工单完成协议出错");
            throw new LogicException("发送工单完成协议出错");
        }
        return true;
    }

    /**
     * 完成任务
     *
     * @param id      任务ID
     * @param lat     纬度
     * @param lon     经度
     * @param addr    地址
     * @return 是否成功完成任务
     * @throws LogicException 如果任务已完成或取消，或者发送工单完成协议出错，将抛出此异常
     */
    @Override
    public boolean completeTask(long id, Double lat, Double lon, String addr) {
        TaskEntity taskEntity = this.getById(id);
        if(Objects.equals(taskEntity.getTaskStatus(), VMSystem.TASK_STATUS_FINISH) || Objects.equals(taskEntity.getTaskStatus(), VMSystem.TASK_STATUS_CANCEL)){
            throw new LogicException("工单已经结束");
        }
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_FINISH);
        taskEntity.setAddr(addr);
        this.updateById(taskEntity);

        //如果是补货工单
        if(Objects.equals(taskEntity.getProductTypeId(), VMSystem.TASK_TYPE_SUPPLY)){
            noticeVMServiceSupply(taskEntity);
        }

        //如果是投放工单或撤机工单
        if(Objects.equals(taskEntity.getProductTypeId(), VMSystem.TASK_TYPE_DEPLOY)
                || Objects.equals(taskEntity.getProductTypeId(), VMSystem.TASK_TYPE_REVOKE)){
            noticeVMServiceStatus(taskEntity,lat,lon);
        }

        return true;
    }




    /**
     * 通知VMService补货
     *
     * @param taskEntity 工单实体
     */
    private void noticeVMServiceSupply(TaskEntity taskEntity){

        //协议内容封装
        //1.根据工单id查询工单明细表
        QueryWrapper<TaskDetailsEntity> qw = new QueryWrapper<>();
        qw.lambda().eq(TaskDetailsEntity::getTaskId,taskEntity.getTaskId());
        List<TaskDetailsEntity> details = taskDetailsService.list(qw);
        //2.构建协议内容
        SupplyCfg supplyCfg = new SupplyCfg();
        supplyCfg.setInnerCode(taskEntity.getInnerCode());//售货机编号
        List<SupplyChannel> supplyChannels = Lists.newArrayList();//补货数据
        //从工单明细表提取数据加到补货数据中
        details.forEach(d->{
            SupplyChannel channel = new SupplyChannel();
            channel.setChannelId(d.getChannelCode());
            channel.setCapacity(d.getExpectCapacity());
            supplyChannels.add(channel);
        });
        supplyCfg.setSupplyData(supplyChannels);

        //2.下发补货协议
        //发送到emq
        try {
            mqttProducer.send( TopicConfig.COMPLETED_TASK_TOPIC,2, supplyCfg );
        } catch (Exception e) {
            log.error("发送工单完成协议出错");
            throw new LogicException("发送工单完成协议出错");
        }
    }




    /**
     * 通知售货机服务状态
     *
     * @param taskEntity 任务实体对象
     * @param lat        纬度
     * @param lon        经度
     */
    private void noticeVMServiceStatus(TaskEntity taskEntity,Double lat,Double lon){
        //向消息队列发送消息，通知售货机更改状态
        //封装协议
        TaskCompleteContract taskCompleteContract=new TaskCompleteContract();
        taskCompleteContract.setInnerCode(taskEntity.getInnerCode());//售货机编号
        taskCompleteContract.setTaskType( taskEntity.getProductTypeId() );//工单类型
        taskCompleteContract.setLat(lat);//纬度
        taskCompleteContract.setLon(lon);//经度
        //发送到emq
        try {
            mqttProducer.send( TopicConfig.COMPLETED_TASK_TOPIC,2, taskCompleteContract );
        } catch (Exception e) {
            log.error("发送工单完成协议出错");
            throw new LogicException("发送工单完成协议出错");
        }
    }

    @Override
    public boolean cancelTask(long id, CancelTaskViewModel cancelVM) {
        TaskEntity task = this.getById(id);
        if(Objects.equals(task.getTaskStatus(), VMSystem.TASK_STATUS_FINISH) || Objects.equals(task.getTaskStatus(), VMSystem.TASK_STATUS_CANCEL)){
            throw new LogicException("该工单已经结束");
        }
        task.setTaskStatus(VMSystem.TASK_STATUS_CANCEL);
        task.setDesc(cancelVM.getDesc());

        return this.updateById(task);
    }

    @Override
    public List<TaskStatusTypeEntity> getAllStatus() {
        QueryWrapper<TaskStatusTypeEntity> qw = new QueryWrapper<>();
        qw.lambda()
                .ge(TaskStatusTypeEntity::getStatusId,VMSystem.TASK_STATUS_CREATE);

        return statusTypeService.list(qw);
    }

    @Override
    public Pager<TaskEntity> search(Long pageIndex, Long pageSize, String innerCode, Integer userId, String taskCode,Integer status,Boolean isRepair,String start,String end) {
        Page<TaskEntity> page = new Page<>(pageIndex,pageSize);
        LambdaQueryWrapper<TaskEntity> qw = new LambdaQueryWrapper<>();
        if(!Strings.isNullOrEmpty(innerCode)){
            qw.eq(TaskEntity::getInnerCode,innerCode);
        }
        if(userId != null && userId > 0){
            qw.eq(TaskEntity::getAssignorId,userId);
        }
        if(!Strings.isNullOrEmpty(taskCode)){
            qw.like(TaskEntity::getTaskCode,taskCode);
        }
        if(status != null && status > 0){
            qw.eq(TaskEntity::getTaskStatus,status);
        }
        if(isRepair != null){
            if(isRepair){
                qw.ne(TaskEntity::getProductTypeId,VMSystem.TASK_TYPE_SUPPLY);
            }else {
                qw.eq(TaskEntity::getProductTypeId,VMSystem.TASK_TYPE_SUPPLY);
            }
        }
        if(!Strings.isNullOrEmpty(start) && !Strings.isNullOrEmpty(end)){
            qw
                    .ge(TaskEntity::getCreateTime,LocalDate.parse(start,DateTimeFormatter.ISO_LOCAL_DATE))
                    .le(TaskEntity::getCreateTime,LocalDate.parse(end,DateTimeFormatter.ISO_LOCAL_DATE));
        }
        //根据最后更新时间倒序排序
        qw.orderByDesc(TaskEntity::getUpdateTime);

        return Pager.build(this.page(page,qw));
    }





    /**
     * 获取同一天内分配的工单最少的人
     * @param innerCode
     * @param isRepair 是否是维修工单
     * @return
     */
    @Override
    public Integer getLeastUser(String innerCode,Boolean isRepair){
        //1.根据售货机编号查询所在区域的用户列表
        List<UserViewModel> userList = null;
        if(isRepair){
            userList = userService.getRepairerListByInnerCode(innerCode);
        }else {
            userList = userService.getOperatorListByInnerCode(innerCode);
        }
        if(userList == null) return null;

        //2.按人分组统计工单表，取工作量
        QueryWrapper<TaskEntity> qw = new QueryWrapper<>();
        qw.select("assignor_id,count(1) as user_id");
        if(isRepair){
            //如果是运维
            qw.lambda().ne(TaskEntity::getProductTypeId, VMSystem.TASK_TYPE_SUPPLY);
        }else {
            //如果是运营
            qw.lambda().eq(TaskEntity::getProductTypeId,VMSystem.TASK_TYPE_SUPPLY);
        }
        qw.lambda()
                .ne(TaskEntity::getTaskStatus,VMSystem.TASK_STATUS_CANCEL) //根据所有未被取消的工单做统计
                .ge(TaskEntity::getCreateTime,LocalDate.now())
                .in(TaskEntity::getAssignorId,userList.stream().map(UserViewModel::getUserId).collect(Collectors.toList()))
                .groupBy(TaskEntity::getAssignorId)
                .orderByAsc(TaskEntity::getUserId);
        List<TaskEntity> result = this.list(qw);

        //3.循环用户列表，找出没有工单的用户
        List<TaskEntity> taskList = Lists.newArrayList();
        Integer userId = 0;
        for (UserViewModel user:userList) {
            Optional<TaskEntity> taskEntity = result.stream().filter(r-> Objects.equals(r.getAssignorId(), user.getUserId())).findFirst();

            //当前人员今日没有分配工单
            if(taskEntity.isEmpty()){
                return user.getUserId();
            }
            TaskEntity item = new TaskEntity();
            item.setAssignorId(user.getUserId());
            item.setUserId(taskEntity.get().getUserId());
            taskList.add(item);
        }
        //4.对列表排序取最少工单的人
        taskList.stream().sorted(Comparator.comparing(TaskEntity::getUserId));
        return taskList.get(0).getAssignorId();
    }


    /**
     * 同一台设备下是否存在未完成的工单
     * @param innerCode
     * @param productionType
     * @return
     */
    private boolean hasTask(String innerCode,int productionType){
        QueryWrapper<TaskEntity> qw = new QueryWrapper<>();
        qw.lambda()
                .select(TaskEntity::getTaskId)
                .eq(TaskEntity::getInnerCode,innerCode)
                .eq(TaskEntity::getProductTypeId,productionType)
                .le(TaskEntity::getTaskStatus,VMSystem.TASK_STATUS_PROGRESS);

        return this.count(qw) > 0;
    }

    /**
     * 检查并创建任务
     *
     * @param innerCode 内部编码
     * @param productType 产品类型
     * @throws LogicException 逻辑异常
     */
    private void checkCreateTask(String innerCode,int productType) throws LogicException {
        VendingMachineViewModel vmInfo = vmService.getVMInfo(innerCode);
        if(vmInfo == null) throw new LogicException("设备校验失败");
        //如果是投放工单，状态为运营
        if(productType == VMSystem.TASK_TYPE_DEPLOY  && Objects.equals(vmInfo.getVmStatus(), VMSystem.VM_STATUS_RUNNING)){
            throw new LogicException("该设备已在运营");
        }
        //如果是补货工单，状态不是运营状态
        if(productType == VMSystem.TASK_TYPE_SUPPLY  && !Objects.equals(vmInfo.getVmStatus(), VMSystem.VM_STATUS_RUNNING)){
            throw new LogicException("该设备不在运营状态");
        }
        //如果是撤机工单，状态不是运营状态
        if(productType == VMSystem.TASK_TYPE_REVOKE  && !Objects.equals(vmInfo.getVmStatus(), VMSystem.VM_STATUS_RUNNING)){
            throw new LogicException("该设备不在运营状态");
        }
    }

    /**
     * 生成工单编码
     */
    private String generateTaskCode(){
        //使用redis原子操作生成唯一编码,反正出现redis重复编码
        //lkd : 这个可以弄成yml配置,防止其他数据污染,这里就不改了
        String key = "lkd.task.code."+LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);

        if(!RedisUtils.hasKey(key)){
            RedisUtils.setCacheObject(key,1L, Duration.ofDays(1));
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))  + 1;
        }

        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")) + RedisUtils.incrAtomicValue(key) ;
    }



}
